/**
 * ***************************************************************************** Copyright (c) 2007,
 * 2008 IBM Corporation and others. All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: IBM Corporation - initial API and implementation
 * *****************************************************************************
 */
package org.eclipse.ltk.core.refactoring.resource;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
import org.eclipse.ltk.internal.core.refactoring.Messages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;

/**
 * {@link Change} that moves a resource.
 *
 * @since 3.4
 */
public class MoveResourceChange extends ResourceChange {

  private final IResource fSource;
  private final IContainer fTarget;
  private final long fStampToRestore;
  private final Change fRestoreSourceChange;

  private ChangeDescriptor fDescriptor;

  /**
   * Creates the change.
   *
   * @param source the resource to move
   * @param target the container the resource is moved to. An existing resource at the destination
   *     will be replaced.
   */
  public MoveResourceChange(IResource source, IContainer target) {
    this(source, target, IResource.NULL_STAMP, null);
  }

  /**
   * Creates the change.
   *
   * @param source the resource to move
   * @param target the container the resource is moved to. An existing resource at the destination
   *     will be replaced.
   * @param stampToRestore the stamp to restore on the moved resource
   * @param restoreSourceChange the change to restore a resource at the source or <code>null</code>
   *     if no resource needs to be resourced.
   */
  protected MoveResourceChange(
      IResource source, IContainer target, long stampToRestore, Change restoreSourceChange) {
    fSource = source;
    fTarget = target;
    fStampToRestore = stampToRestore;
    fRestoreSourceChange = restoreSourceChange;

    // We already present a dialog to the user if he
    // moves read-only resources. Since moving a resource
    // doesn't do a validate edit (it actually doesn't
    // change the content we can't check for READ only
    // here.
    setValidationMethod(VALIDATE_NOT_DIRTY);
  }

  /* (non-Javadoc)
   * @see org.eclipse.ltk.core.refactoring.Change#getDescriptor()
   */
  public ChangeDescriptor getDescriptor() {
    return fDescriptor;
  }

  /**
   * Sets the change descriptor to be returned by {@link Change#getDescriptor()}.
   *
   * @param descriptor the change descriptor
   */
  public void setDescriptor(ChangeDescriptor descriptor) {
    fDescriptor = descriptor;
  }

  /* (non-Javadoc)
   * @see org.eclipse.ltk.core.refactoring.Change#perform(org.eclipse.core.runtime.IProgressMonitor)
   */
  public final Change perform(IProgressMonitor monitor)
      throws CoreException, OperationCanceledException {
    try {
      if (monitor == null) monitor = new NullProgressMonitor();

      monitor.beginTask(getName(), 4);

      Change deleteUndo = null;

      // delete destination if required
      IResource resourceAtDestination = fTarget.findMember(fSource.getName());
      if (resourceAtDestination != null && resourceAtDestination.exists()) {
        deleteUndo =
            performDestinationDelete(resourceAtDestination, new SubProgressMonitor(monitor, 1));
      } else {
        monitor.worked(1);
      }

      // move resource
      long currentStamp = fSource.getModificationStamp();
      IPath destinationPath = fTarget.getFullPath().append(fSource.getName());
      fSource.move(
          destinationPath,
          IResource.KEEP_HISTORY | IResource.SHALLOW,
          new SubProgressMonitor(monitor, 2));
      resourceAtDestination = ResourcesPlugin.getWorkspace().getRoot().findMember(destinationPath);

      // restore timestamp at destination
      if (fStampToRestore != IResource.NULL_STAMP) {
        resourceAtDestination.revertModificationStamp(fStampToRestore);
      }

      // restore file at source
      if (fRestoreSourceChange != null) {
        performSourceRestore(new SubProgressMonitor(monitor, 1));
      } else {
        monitor.worked(1);
      }
      return new MoveResourceChange(
          resourceAtDestination, fSource.getParent(), currentStamp, deleteUndo);
    } finally {
      monitor.done();
    }
  }

  private Change performDestinationDelete(IResource newResource, IProgressMonitor monitor)
      throws CoreException {
    monitor.beginTask(RefactoringCoreMessages.MoveResourceChange_progress_delete_destination, 3);
    try {
      DeleteResourceChange deleteChange = new DeleteResourceChange(newResource.getFullPath(), true);
      deleteChange.initializeValidationData(new SubProgressMonitor(monitor, 1));
      RefactoringStatus deleteStatus = deleteChange.isValid(new SubProgressMonitor(monitor, 1));
      if (!deleteStatus.hasFatalError()) {
        return deleteChange.perform(new SubProgressMonitor(monitor, 1));
      }
      return null;
    } finally {
      monitor.done();
    }
  }

  private void performSourceRestore(IProgressMonitor monitor) throws CoreException {
    monitor.beginTask(RefactoringCoreMessages.MoveResourceChange_progress_restore_source, 3);
    try {
      fRestoreSourceChange.initializeValidationData(new SubProgressMonitor(monitor, 1));
      RefactoringStatus restoreStatus =
          fRestoreSourceChange.isValid(new SubProgressMonitor(monitor, 1));
      if (!restoreStatus.hasFatalError()) {
        fRestoreSourceChange.perform(new SubProgressMonitor(monitor, 1));
      }
    } finally {
      monitor.done();
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ltk.core.refactoring.resource.ResourceChange#getModifiedResource()
   */
  protected IResource getModifiedResource() {
    return fSource;
  }

  /* (non-Javadoc)
   * @see org.eclipse.ltk.core.refactoring.Change#getName()
   */
  public String getName() {
    return Messages.format(
        RefactoringCoreMessages.MoveResourceChange_name,
        new String[] {
          BasicElementLabels.getPathLabel(fSource.getFullPath(), false),
          BasicElementLabels.getResourceName(fTarget)
        });
  }
}
